﻿using System.Collections.Generic;
using System.Reflection;
using MetaSharp.Transformation.Patterns;

namespace MetaSharp.Transformation
{
    public class Scope : IScope
    {
        public IStream Stream { get; private set; }
        public ImmutableStack<RuleEntry> RuleStack { get; private set; }
        public Map<string, object> Variables { get; private set; }
        public MemoTable Memos { get; private set; }

        public Scope(IStream stream) : this(
            stream,
            new ImmutableStack<RuleEntry>(),
            Map<string, object>.Empty,
            MemoTable.Empty)
        {
        }

        public Scope(
            IStream stream,
            ImmutableStack<RuleEntry> ruleStack,
            Map<string, object> variables,
            MemoTable memos)
        {
            this.Stream = stream;
            this.RuleStack = ruleStack;
            this.Variables = variables;
            this.Memos = memos;
        }

        public IScope PushRule(PatternMatch rule, RulePattern origin)
        {
            var memos = this.Memos.Add(new MemoKey(this.Stream, origin), new Memo(Match.LR(this), origin, new ArrayList<Memo>()));
            return new Scope(
                this.Stream,
                this.RuleStack.Push(new RuleEntry(rule, origin, this.Stream, memos)),
                this.Variables,
                memos);
        }

        public IScope PopRule(IMatch match)
        {
            var top = this.RuleStack.Peek();
            var references = this.Memos.NewerThan(top.Memos);
            var memos = this.Memos.Add(new MemoKey(top.Stream, top.Origin), new Memo(match, top.Origin, references));
            return new Scope(
                this.Stream,
                this.RuleStack.Pop(),
                this.Variables,
                memos);
        }

        public IScope AddVariable(string name, object value)
        {
            return new Scope(
                this.Stream,
                this.RuleStack,
                this.Variables.Add(name, value),
                this.Memos);
        }

        public IScope AddVariables(Map<string, object> variables)
        {
            var vars = this.Variables;
            foreach (var variable in variables)
                vars = vars.Add(variable.Key, variable.Value);

            return new Scope(
                this.Stream,
                this.RuleStack,
                vars,
                this.Memos);
        }

        public IScope SetMemo(IMatch match, IStream start, IStream end, PatternMatch pattern, RulePattern origin)
        {
            var key = new MemoKey(start, origin);
            IImmutableArray<Memo> references = new ArrayList<Memo>();
            Memo current;
            if (this.Memos.TryGet(key, out current))
                references = current.References;

            var memos = this.Memos.Add(key, new Memo(match, origin, references));
            return new Scope(
                this.Stream,
                this.RuleStack,
                this.Variables,
                memos);
        }

        public IScope SetMemos(MemoTable memos)
        {
            return new Scope(
                this.Stream,
                this.RuleStack,
                this.Variables,
                memos);
        }

        public IScope SetStream(IStream stream)
        {
            return new Scope(
                stream,
                this.RuleStack,
                this.Variables,
                this.Memos);
        }

        public IScope SetVariables(Map<string, object> variables)
        {
            return new Scope(
                this.Stream,
                this.RuleStack,
                variables,
                this.Memos);
        }
    }
}
